#ifndef MEMORIACOMPARTIDA_H_
#define MEMORIACOMPARTIDA_H_

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string>
#include <string.h>
#include <iostream>
#include <sstream>
#include <errno.h>
#include <sched.h>
#include <unistd.h>

#include <BaseException.h>
#include <Semaforo.h>
#include <Constantes.h>

template <class T> class MemoriaCompartida {

private:
	int	shmId;
	T*	m_PtrDatos;
	std::string m_Archivo;
	char m_ProjectId;
	Semaforo m_Semaforo;
	pid_t m_AttacherPid;

	int	cantidadProcesosAdosados() const;
	void obtenerMemoria( const std::string& archivo, bool crearMemoria, const char projectId);
	void attach( const std::string& archivo );
	int dettach();

public:
	MemoriaCompartida ();
	void crear ( const std::string& archivo, const char projectId = ProjectId::MEMORIA_COMPARTIDA);
	void unirse ( const std::string& archivo, const char projectId = ProjectId::MEMORIA_COMPARTIDA);
	int liberar();
	
	bool estaCreada();
	
	MemoriaCompartida ( const std::string& archivo, const char projectId = ProjectId::MEMORIA_COMPARTIDA);
	MemoriaCompartida ( const MemoriaCompartida& origen );
	~MemoriaCompartida ();
	MemoriaCompartida<T>& operator= ( const MemoriaCompartida& origen );
	
	// Ninguno de los siguientes metodos debe llamarse 
	// sin previamente haber llamado al metodo crear o unirse
	
	// Permite manejar el sincronismo de manera externa
	void lock();
	void escribir ( const T& dato );
	T leer () const;
	void unlock();
	
	// Manejar el sincronismo internamente
	void escribirSincronizado( const T& dato );
	T leerSincronizado() const;
};

template <class T> MemoriaCompartida<T>::MemoriaCompartida ()
:shmId(-1),m_PtrDatos(NULL),m_ProjectId(ProjectId::MEMORIA_COMPARTIDA),m_AttacherPid(0) {
}

template <class T> void MemoriaCompartida<T>::crear ( const std::string& archivo, const char projectId ) {
	bool crearMemoria = true;
	m_Semaforo.crear(archivo, 1);
	try
	{
		m_Semaforo.p();
		this->obtenerMemoria(archivo, crearMemoria, projectId);
		this->attach(archivo);
		m_Semaforo.v();
	}
	catch(const BaseException& e)
	{
		m_Semaforo.v();
		m_Semaforo.eliminar();
		throw e;
	}
}

template <class T> void MemoriaCompartida<T>::unirse ( const std::string& archivo, const char projectId ) {
	bool crearMemoria = false;
	m_Semaforo.abrir(archivo);
	try
	{
		m_Semaforo.p();
		this->obtenerMemoria(archivo, crearMemoria, projectId);
		this->attach(archivo);
		m_Semaforo.v();
	}
	catch(const BaseException& e)
	{
		m_Semaforo.v();
		throw e;
	}
}

template <class T> int MemoriaCompartida<T>::liberar() {
	return this->dettach();
}

template <class T> bool MemoriaCompartida<T>::estaCreada()
{
	return (this->m_PtrDatos != NULL);
}

template <class T> MemoriaCompartida<T>::MemoriaCompartida ( const std::string& archivo, const char projectId )
:shmId(-1),m_PtrDatos(NULL),m_AttacherPid(0) {
	this->crear(archivo, projectId);
}

template <class T> MemoriaCompartida<T>::MemoriaCompartida ( const MemoriaCompartida& origen )
:shmId(origen.shmId),m_PtrDatos(NULL),m_AttacherPid(0) {
	this->unirse(origen.m_Archivo, origen.m_ProjectId);
}

template <class T> MemoriaCompartida<T>::~MemoriaCompartida () {
	this->dettach();
}

template <class T> MemoriaCompartida<T>& MemoriaCompartida<T>::operator= ( const MemoriaCompartida& origen ) {
	this->shmId = origen.shmId;
	this->m_AttacherPid = 0;
	m_PtrDatos = NULL;
	this->attach(origen.m_Archivo);
	return *this;
}

template <class T> void MemoriaCompartida<T>::lock() {
	if(this->m_PtrDatos == NULL)
		throw BaseException("La memoria no fue inicializada");
	
	m_Semaforo.p();
}

template <class T> void MemoriaCompartida<T>::escribir( const T& dato ) {
	if(this->m_PtrDatos == NULL)
		throw BaseException("La memoria no fue inicializada");
	
	*(this->m_PtrDatos) = dato;
}

template <class T> T MemoriaCompartida<T>::leer() const {
	if(this->m_PtrDatos == NULL)
		throw BaseException("La memoria no fue inicializada");
	
	return *(this->m_PtrDatos);
}

template <class T> void MemoriaCompartida<T>::unlock() {
	if(this->m_PtrDatos == NULL)
		throw BaseException("La memoria no fue inicializada");
	
	m_Semaforo.v();
}

template <class T> void MemoriaCompartida<T>::escribirSincronizado( const T& dato ) {
	if(this->m_PtrDatos == NULL)
		throw BaseException("La memoria no fue inicializada");
	
	m_Semaforo.p();
	*(this->m_PtrDatos) = dato;
	m_Semaforo.v();
}

template <class T> T MemoriaCompartida<T>::leerSincronizado() const {
	if(this->m_PtrDatos == NULL)
		throw BaseException("La memoria no fue inicializada");
	
	m_Semaforo.p();
	T datoLeido = *(this->m_PtrDatos);
	m_Semaforo.v();
	
	return datoLeido;
}

template <class T> int MemoriaCompartida<T> :: cantidadProcesosAdosados () const {
	if(this->shmId < 0)
		throw BaseException("La memoria no fue debidamente inicializada");
	
	shmid_ds estado;
	shmctl ( this->shmId,IPC_STAT,&estado );
	return estado.shm_nattch;
}

template <class T> void MemoriaCompartida<T>::obtenerMemoria( const std::string& archivo, bool crearMemoria, const char projectId ) {
	if(this->shmId > 0)
		throw BaseException("La memoria esta en uso");
	
	m_ProjectId = projectId;
	
	key_t clave = ftok ( archivo.c_str(), projectId);
	if ( clave > 0 ) {
		int shmflag = (crearMemoria) ? 0644|IPC_CREAT|IPC_EXCL : 0644;
		
		this->shmId = shmget ( clave, sizeof(T), shmflag);

		if ( this->shmId < 0 ) {
			std::stringstream eMsg;
			eMsg << "Error " << errno << " en shmget(): " << strerror(errno);
			throw BaseException(eMsg.str());
		}
	} else {
		std::stringstream eMsg;
		eMsg << "Ocurrio un error al obtener la clave para el archivo " << archivo;
		eMsg << ". Error " << errno << " en ftok(): " << strerror(errno);
		throw BaseException(eMsg.str());
	}
}

template <class T> void MemoriaCompartida<T>::attach ( const std::string& archivo ) {
	if(this->shmId < 0)
		throw BaseException("La memoria no fue debidamente inicializada");
	
	if(m_PtrDatos != NULL && m_AttacherPid == getpid())
		throw BaseException("La memoria ya esta asociada al proceso");
	
	void* tmpPtr = shmat ( this->shmId,NULL,0 );
	if ( tmpPtr != (void*) -1 ) {
		// Me guardo el nombre del archivo que identifica a la memoria
		m_Archivo = archivo;
		
		// Guardo el pid del proceso que realizo el attach
		m_AttacherPid = getpid();
		
		// Me guardo la referencia a la memoria compartida
		this->m_PtrDatos = static_cast<T*> (tmpPtr);
	} else {
		std::string mensaje = std::string("Error en shmat(): ") + std::string(strerror(errno));
		throw BaseException(mensaje);
	}
}

template <class T> int MemoriaCompartida<T>::dettach() {
	int errorDt = 0;
	if(m_PtrDatos != NULL && this->shmId >= 0 && this->m_AttacherPid == getpid())
	{
		try
		{
			m_Semaforo.p();
			errorDt = shmdt ( (void *) this->m_PtrDatos );
			if ( errorDt != -1 )
			{
				m_PtrDatos = NULL;
				
				int procAdosados = this->cantidadProcesosAdosados ();
				if ( procAdosados == 0 )
				{
					shmctl ( this->shmId,IPC_RMID,NULL );
					
					// Elimino el semaforo que ya no se utilizara
					m_Semaforo.eliminar();
				}
				else
				{
					m_Semaforo.v();
				}
				this->shmId = -1;
				m_Semaforo.destruir();
			}
			else
			{
				std::cerr << "Error en shmdt(): " << strerror(errno) << std::endl;
				m_Semaforo.v();
			}
		}
		catch(const BaseException& e)
		{
			std::cerr << "Error en dettach(): " << e.what() << std::endl;
			m_Semaforo.v();
		}
	}
	m_PtrDatos = NULL;
	this->shmId = -1;
	m_Semaforo.destruir();
	m_AttacherPid = 0;
	
	return errorDt;
}


#endif
